/*
 * Copyright (c) 2008, Martin Novák (http://www.kronos-software.eu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Martin Novák nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Martin Novák ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Martin Novák BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
var mn;
if(typeof mn == "undefined" || !mn) {
  mn = {};
}

mn.validation = {};
mn.validation.localization = {};

/**
 * Counts selected items at html select form element
 */
mn.validation.countSelectSelectedItems = function(element)
{
  var i, count = 0;
  for(i = 0; i < element.childNodes.length; ++i) {
    var child = element.childNodes[i];
    if(child.nodeType == 1 && child.tagName === "OPTION") {
      if(child.selected) {
        ++count;
      }
    }
  }
  return count;
}

mn.validation.countCheckedRadios = function(radios)
{
  var count = 0;
  for(var i = 0; i < radios.length; ++i) {
    if(radios[i].checked) {
      count++;
    }
  }
  return count;
}

mn.validation.textValue = function(element)
{
  if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') {
    return element.value;
  }
  return null;  
}

mn.validation.textValueLength = function(element)
{
  if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') {
    return element.value.length;
  }
  return 0;
}

mn.validation.errorMessage = function(msg, altMsg, label, p1, p2, p3)
{
  if(label) {
    var r = mn.validation.currentLocale[msg];
    if(!r)
      r = msg;
    r = r.replace("${0}", label);
  }
  else {
    var r = mn.validation.currentLocale[altMsg];
    if(!r)
      r = altMsg;
  }
  r = r.replace('${1}', p1).replace('${2}', p2).replace('${3}', p3);
  return r;
}

mn.validation.localization.en = {
  required :"${0} is required",
  requiredalt :"Item is required",
  requiredselect :"At least one item must be selected",
  requiredradio :"Select ${0}",
  requiredradioalt: "Select one option",
  minlen :"${0} must be at least ${1} characters long",
  minlenalt :"Item must be at least ${1} characters long",
  minlenselect :"At least ${1} items must be selected",
  minlenselectalt :"At least ${1} items must be selected",
  maxlen :"${0} must be at most ${1} characters long",
  maxlenalt :"Item must be at most ${1} characters long",
  maxlenselect :"At most ${1} items can be selected",
  maxlenselectalt :"At most ${1} items can be selected",
  minvalue :"${0} must be at least ${1}",
  minvaluealt :"Item must be at least ${1}",
  maxvalue :"${0} must be at most ${1}",
  maxvaluealt :"Item must be at most ${1}",
  intvalue :"${0} must be a number",
  intvaluealt :"Item must be a number",
  floatvalue: "${0} must be a number",
  floatvaluealt: "Item must be a number",
  email: "\"${1}\" is not a valid email",
  emailalt: "Item is not a valid email",
  pwdmatch: "Passwords do not match",
  pwdmatchalt: "Passwords do not match",
  processing: "Processing"
};

mn.validation.localization.cz = {
  required         : "${0} nesmí být prázdné",
  requiredalt      : "Text nesmí být prázdný",
  requiredselect   : "Vyberte hodnotu",
  requiredradio    : "Vyberte ${0}",
  requiredradioalt : "Vyberte hodnotu",
  minlen           : "${0} musí mít nejméně ${1} znaků",
  minlenalt        : "Hodnota musí mít nejméně ${1} znaků",
  minlenselect     : "Vybráno musí být alespoň ${1} položek",
  minlenselectalt  : "Vybráno musí být alespoň ${1} položek",
  maxlen           : "${0} může mít nejvýše ${1} znaků",
  maxlenalt        : "Hodnota může mít nejvýše ${1} znaků",
  maxlenselect     : "Můžete vybrat nejvýše ${1} položek",
  maxlenselectalt  : "Můžete vybrat nejvýše ${1} položek",
  minvalue         : "${0} musí být alespoň ${1}",
  minvaluealt      : "Hodnota musí být alespoň ${1}",
  maxvalue         : "${0} musí být maximálně ${1}",
  maxvaluealt      : "Hodnota musí být maximálně ${1}",
  intvalue         : "${0} musí být číslo",
  intvaluealt      : "Hodnota musí být číslo",
  floatvalue       : "${0} musí být číslo",
  floatvaluealt    : "Políčko musí být číslo",
  email            : "\"${1}\" není email",
  emailalt         : "\"${1}\" není email",
  pwdmatch         : "Hesla se neshodují",
  pwdmatchalt      : "Hesla se neshodují",
  processing       : "Zpracovávám"
};
mn.validation.localization.cz.prototype = mn.validation.localization.en;

mn.validation.allowedTags = {
  INPUT :"INPUT",
  TEXTAREA :"TEXTAREA",
  SELECT :"SELECT"
};

mn.validation.currentLocale = mn.validation.localization.en;

mn.validation.Validator = function()
{
  this.validators = {};
  this.errors = {};
  this.errorsCount = 0;
  var that = this;
  
  // Initialize some basic validators
  this.addGenericValidator('val_required', 0, function(label, element) {
    if(element instanceof Array) {
      if(mn.validation.countCheckedRadios(element) == 0) {
        return mn.validation.errorMessage('requiredradio', 'requiredradioalt', label);
      }
      return null;
    }
    if(element.tagName === "SELECT") {
      if(mn.validation.countSelectSelectedItems(element) == 0)
        return mn.validation.errorMessage('requiredselect', 'requiredselect', label);
    }
    else {
      if(mn.validation.textValueLength(element) == 0)
        return mn.validation.errorMessage('required', 'requiredalt', label);
    }
    return null;
  });
  this.addGenericValidator('val_minlen', 10, function(label, element, minlen) {
    if(element.tagName === "SELECT") {
      if(mn.validation.countSelectSelectedItems(element) < minlen)
        return mn.validation.errorMessage('minlenselect', 'minlenselectalt', label, minlen);
    }
    else {
      if(mn.validation.textValueLength(element) < minlen)
        return mn.validation.errorMessage('minlen', 'minlenalt', label, minlen);
    }
    return null;
  });
  this.addGenericValidator('val_maxlen', 10, function(label, element, maxlen) {
    if(element.tagName === "SELECT") {
      if(mn.validation.countSelectSelectedItems(element) > maxlen)
        return mn.validation.errorMessage('maxlenselect', 'maxlenselectalt', label, maxlen);
    }
    else {
      if(mn.validation.textValueLength(element) > maxlen)
        return mn.validation.errorMessage('maxlen', 'maxlenalt', label, maxlen);
    }
    return null;
  });
  this.addGenericValidator('val_minvalue', 10, function(label, element, minvalue) {
    if(parseInt(mn.validation.textValue(element), 10) < minvalue)
      return mn.validation.errorMessage('minvalue', 'minvaluealt', label, minvalue);
    return null;
  });
  this.addGenericValidator('val_maxvalue', 10, function(label, element, maxvalue) {
    if(parseInt(mn.validation.textValue(element), 10) > maxvalue)
      return mn.validation.errorMessage('maxvalue', 'maxvaluealt', label, maxvalue);
    return null;
  });
  this.addGenericValidator('val_int', 10, function(label, element, maxvalue) {
    if(isNaN(parseInt(mn.validation.textValue(element), 10)))
      return mn.validation.errorMessage('intvalue', 'intvaluealt', label, maxvalue);
    return null;
  });
  this.addGenericValidator('val_float', 10, function(label, element, maxvalue) {
    if(isNaN(parseFloat(mn.validation.textValue(element))))
      return mn.validation.errorMessage('floatvalue', 'floatvaluealt', label, maxvalue);
    return null;
  });
  this.addGenericValidator('val_email', 10, function(label, element, maxvalue) {
    var filter=/^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
    var email = mn.validation.textValue(element);
    if(filter.test(email) == false)
      return mn.validation.errorMessage('email', 'emailalt', label, email);
    return null;
  });
  this.addGenericValidator('val_passwordcheck', 10, function(label, element, maxvalue) {
    var otherElement = null;
    for(var i = 0; i < that.form.elements.length; ++i) {
      var fi = that.form.elements[i];
      if(fi.el.tagName == 'INPUT' && fi.el.type == 'password' && fi.el != element) {
        otherElement = fi;
        break;
      }
    }
    if(!otherElement)
      throw "There is must be some more password controls in the form.";
    if(! (mn.validation.textValue(element) === mn.validation.textValue(otherElement.el)) )
      return mn.validation.errorMessage('pwdmatch', 'pwdmatchalt', label, maxvalue);
    return null;
  });

  return this;
}

mn.validation.Validator.prototype.clearErrors = function()
{
  this.validators = [];
  this.fields = [];
}

mn.validation.Validator.prototype.addError = function(name, error)
{
  var errs = mn.validation.errors[name];
  if(!errs) {
    errs = [];
    mn.validation.errors[name] = errs;
  }

  errs[errs.length] = error;
}

mn.validation.Validator.prototype.checkForm = function(form)
{
  mn.validation.errors = {};
  mn.validation.errorsCount = 0;
  
  this.form = form;
  
  this.loadFormParameters(form);
  
  var forFocus = [];
  
  for(var i = 0; i < form.elements.length; ++i) {
    var fi = form.elements[i];
    var el = fi.el;
    if(!mn.validation.allowedTags[el.tagName])
      continue;
    if(el.type === "hidden" || el.type === "submit" || el.type === "button")
      continue;
      
      if(this.validateFormElement(form, fi) == false)
        forFocus[forFocus.length] = el;
  }

  if(forFocus.length > 0) {
    var focusEl = forFocus[0];
    var focusPos = focusEl.position;
    if(!focusPos)
      focusPos = 999999;

    for(i = 1; i < forFocus.length; ++i) {
      if(forFocus[i].position < focusPos) {
        focusEl = forFocus[i];
        focusPos = focusEl.position;
      }
    }

    if(this.tabView) {
      var yuiTabContent = this.getYUI2TabContent(focusEl);
      if(yuiTabContent) {
        var tabIndex = this.getYUI2TabContentIndex(yuiTabContent);
        this.tabView.set('activeIndex', tabIndex);
      }
    }
    focusEl.focus();
  }
  else if(this.disable_submits) {
    this.disableAllSubmits();
  }
}

mn.validation.Validator.prototype.getYUI2TabContent = function(el)
{
  var prevParent = el;
  var parent = el.parentNode;
  while(parent && parent != document.body) {
    if(parent.className.indexOf('yui-content') >= 0) {
      return prevParent;
    }
    
    prevParent = parent;
    parent = parent.parentNode;
  }
}

mn.validation.Validator.prototype.getYUI2TabContentIndex = function(tabContent)
{
  var index = 0;
  var parent = tabContent.parentNode;
  for(var i = 0; i < parent.childNodes.length; ++i) {
    var child = parent.childNodes[i];
    if(child.tagName == 'DIV') {
      if(child == tabContent) {
        return index;
      }
      
      index++;
    }
  }
}

mn.validation.Validator.prototype.loadFormParameters = function(form)
{
  this.disable_submits = true;
  this.lang = mn.validation.localization['en'];
  
  for(var i = 0; i < form.validations.length; ++i) {
    var val = form.validations[i];
    if(val.name === 'val_no_disable_submits') {
      this.disable_submits = false;
    }
    else if(val.name === 'val_lang') {
      var lang = val.params[0];
      if(!lang)
        throw 'You forgot to specify "var_lang" validation parameter. Write it this way: <input class="var_lang-en">.';
      if(!mn.validation.localization[lang])
        throw 'Invalid localization: ' + lang;
      this.lang = mn.validation.localization[lang];
    }
  }
}

mn.validation.Validator.prototype.disableAllSubmits = function()
{
  for(var i = 0; i < this.form.elements.length; ++i) {
    var el = this.form.elements[i];
    if(el.el.tagName === 'INPUT' && el.el.type === 'submit') {
      el.el.disabled = true;
      el.el.value = this.lang.processing;
    }
  }
}

mn.validation.Validator.prototype.validateFormElement = function(form, fi)
{
  var validations = fi.validations;
  var label = fi.label;

  if(!validations || validations.length == 0)
    return true;

  for( var i = 0; i < validations.length; ++i) {
    var val = validations[i];
    var valName = val[0];
    var valObj = this.validators[val.name];
    var msg = null;
    if(valObj) {
      if(!fi.els)
        fi.els = fi.el;
      msg = valObj.fn(label, fi.els, val.params[0], val.params[1], val.params[2], val.params[3], val.params[4]);
    }
    
    if(msg) {
      this.appendError(fi, msg);

      this.errorsCount++;
      return false;
    }
  }
  
  // Clear the error at the end
  this.appendError(fi, null);

  return true;
}

mn.validation.Validator.prototype.appendError = function(fi, msg)
{
  if(fi.errorPosition.pos == 'before')
    this.appendErrorBeforeElement(fi.el, msg);
  else if(fi.errorPosition.pos == 'inside')
    this.appendErrorInside(fi.el, fi.errorPosition.id, msg);
  else
    this.appendErrorBehindElement(fi.el, msg);
}

/**
 * Appends a div with an error behind the element. Removes the div if the msg is null.
 */
mn.validation.Validator.prototype.appendErrorBehindElement = function(element, msg)
{
  var errId = element.id + '_' + 'error';

  var newEl = document.getElementById(errId);
  if(msg && msg.length > 0) {
    if(newEl) {
      var parentEl = newEl.parentNode;
      parentEl.removeChild(newEl);
    }

    parentEl = element.parentNode;
    newEl = document.createElement('div');
    newEl.id = errId;
    newEl.className = "error-message";
    parentEl.appendChild(newEl);

    // remove all child elements from the error node
    var child = newEl.firstChild;
    while(child) {
      newEl.removeChild(child);
      child = newEl.firstChild;
    }

    var textNode = document.createTextNode(msg);
    newEl.appendChild(textNode);
  }
  else 
    if(newEl) {
      var parentEl = newEl.parentNode;
      parentEl.removeChild(newEl);
    }
}

mn.validation.Validator.prototype.addGenericValidator = function(aprefix, apriority, afn)
{
  this.validators[aprefix] = { prefix: aprefix, priority: apriority, fn: afn };
}

var YUI;
if(YUI) {
  YUI().use('node', function(Y) {
     function init() {
       var autoForms = [];
       var forms = document.forms;
       for(var i = 0; i < forms.length; ++i) {
         var form = forms[i];
         var clz = form.className;
         if(!clz || clz.length == 0)
           continue;

         var clzs = clz.split(" ");
         for(var i = 0; i < clzs.length; ++i) {
           if(clzs[i] === 'val_auto') {
             autoForms[autoForms.length] = form.name;
             break;
           }
         }
       }
       
       var fn = function(e, form) {
         var val = new mn.validation.Validator();
         val.checkForm(form);
         e.cancelBubble = true;
         e.returnValue = false;
         e.preventDefault();
         return false;
       }
       for(var i = 0; i < autoForms.length; ++i) {
         var form = document.forms[autoForms[i]];
         var formObject = new mn.forms.Form(form.name);
         for(var j = 0; j < form.elements.length; ++j) {
           var el = form.elements[j];
           if(el.tagName === 'INPUT' && el.type === 'submit') {
             Y.on("click", fn, el, {}, formObject);
           }
         }
       }
     }
     Y.on("event:ready", init);
  });
}
